package by.dkusch.aircompany.dao.impl;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.SingularAttribute;

import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.stereotype.Repository;

import by.dkusch.aircompany.dao.RouteDao;
import by.dkusch.aircompany.model.filter.FilterRoute;
import by.dkusch.aircompany.model.filter.base.FilterEntity;
import by.dkusch.aircompany.model.tables.Airport;
import by.dkusch.aircompany.model.tables.Airport_;
import by.dkusch.aircompany.model.tables.Flight;
import by.dkusch.aircompany.model.tables.Flight_;
import by.dkusch.aircompany.model.tables.Route;
import by.dkusch.aircompany.model.tables.Route_;
import by.dkusch.aircompany.model.tables.TimeTable;
import by.dkusch.aircompany.model.tables.TimeTable_;
import by.dkusch.aircompany.model.tables.base.BaseEntity_;

@Repository
public class RouteDaoImpl extends AbstractDaoImpl<Long, Route> implements RouteDao {

	public RouteDaoImpl() {
		super(Route.class);
	}

	@Override
	public List<Route> getAllItemsWithFilter(SingularAttribute<? super Route, ?> attr, boolean ascending, int startRecord, int pageSize, FilterEntity<Long, ? super Route> filter) {
		if (filter == null) {
			return getAllItems(attr, ascending, startRecord, pageSize);
		}

		CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();
		FilterRoute fc = (FilterRoute) filter;

		CriteriaQuery<Route> criteria = cBuilder.createQuery(Route.class);
		Root<Route> fromRoute = criteria.from(Route.class);

		@SuppressWarnings("unchecked")
		Join<Route, Airport> fromAirport = (Join<Route, Airport>) fromRoute.fetch(Route_.startPoint, JoinType.LEFT);
		@SuppressWarnings("unchecked")
		Join<Route, Airport> toAirport = (Join<Route, Airport>) fromRoute.fetch(Route_.endPoint, JoinType.LEFT);

		OrderImpl order = new OrderImpl(fromRoute.get(BaseEntity_.id), ascending);
		if (attr != null) {
			if (Route_.startPoint.equals(attr)) {
				order = new OrderImpl(fromAirport.get(Airport_.code), ascending);
			} else if (Route_.endPoint.equals(attr)) {
				order = new OrderImpl(toAirport.get(Airport_.code), ascending);
			} else {
				order = new OrderImpl(fromRoute.get(attr), ascending);
			}
		}
		
		Subquery<TimeTable> subQuery = criteria.subquery(TimeTable.class);
		Root<TimeTable> subFromTimeTable = subQuery.from(TimeTable.class);
		
		List<Predicate> subConditions = new ArrayList<Predicate>();
		subConditions.add(cBuilder.equal(subFromTimeTable.get(TimeTable_.route), fromRoute.get(Route_.id)));
		
		if ((fc.getDepartureTimeStart() != null) && (fc.getDepartureTimeEnd() != null)) {
			subConditions.add(cBuilder.between(subFromTimeTable.get(TimeTable_.departureTime), fc.getDepartureTimeStart(), fc.getDepartureTimeEnd()));
		} else if ((fc.getDepartureTimeStart() != null) && (fc.getDepartureTimeEnd() == null)) {
			subConditions.add(cBuilder.greaterThanOrEqualTo(subFromTimeTable.get(TimeTable_.departureTime), fc.getDepartureTimeStart()));
		} else if ((fc.getDepartureTimeStart() == null) && (fc.getDepartureTimeEnd() != null)) {
			subConditions.add(cBuilder.lessThanOrEqualTo(subFromTimeTable.get(TimeTable_.departureTime), fc.getDepartureTimeEnd()));
		}
		
		subQuery.select(subFromTimeTable).where(cBuilder.and(subConditions.toArray(new Predicate[] {})));
		
		List<Predicate> conditions = new ArrayList<Predicate>();

		if (fc.getCode() != null) {
			conditions.add(cBuilder.equal(cBuilder.upper(fromRoute.get(Route_.code)), fc.getCode().toUpperCase()));
		}

		if (fc.getName() != null) {
			conditions.add(cBuilder.like(cBuilder.upper(fromRoute.get(Route_.name)), fc.getName().toUpperCase()));
		}

		if (fc.getStartAirportCode() != null) {
			conditions.add(cBuilder.like(cBuilder.upper(fromAirport.get(Airport_.code)), fc.getStartAirportCode().toUpperCase()));
		}

		if (fc.getEndAirportCode() != null) {
			conditions.add(cBuilder.like(cBuilder.upper(toAirport.get(Airport_.code)), fc.getEndAirportCode().toUpperCase()));
		}

		if ((fc.getDurationMin() != null) || (fc.getDurationMax() != null)) {
			conditions.add(cBuilder.between(fromRoute.get(Route_.duration), fc.getDurationMin() == null ? 0 : fc.getDurationMin(),
					fc.getDurationMax() == null ? Integer.MAX_VALUE : fc.getDurationMax()));
		}

		if ((fc.getDepartureTimeStart() != null) || (fc.getDepartureTimeEnd() != null)) {
			conditions.add(cBuilder.exists(subQuery));
		}
		TypedQuery<Route> query = getEm().createQuery(criteria.select(fromRoute).where(cBuilder.and(conditions.toArray(new Predicate[] {}))).orderBy(order));

		if (startRecord >= 0) {
			query.setFirstResult(startRecord);
			query.setMaxResults(pageSize);
		}

		List<Route> results = query.getResultList();
		return results;

	}

	@Override
	public boolean checkCanRemoveRecord(Route entity) {
		CriteriaBuilder cBuilder = getEm().getCriteriaBuilder();
		CriteriaQuery<Flight> criteria = cBuilder.createQuery(Flight.class);

		Root<Flight> fromFlight = criteria.from(Flight.class);
		Join<Flight, Route> fromRoute = fromFlight.join(Flight_.route);

		List<Predicate> conditions = new ArrayList<Predicate>();
		conditions.add(cBuilder.equal(fromRoute.get(Route_.id), entity.getId()));

		TypedQuery<Flight> query = getEm().createQuery(criteria.select(fromFlight).where(cBuilder.and(conditions.toArray(new Predicate[] {}))));
		List<Flight> results = query.getResultList();
		return (results == null) || ((results != null) && (results.size() == 0));
	}
}
